﻿namespace SoftwarePackages

module PackageSourceListModule

open System
open System.IO
open System.Text

let Dispose(disposable: 'a when 'a :> IDisposable) =
    disposable.Dispose()

type VersionInformation =
    {   IsVirtual:      bool;
        Dependencies:   Map<string, Date>;
        Sources:        string list;    }

type PackageList(sourcesDirectory, readonly) =
    let packagePath packageName =
        Path.Combine(sourcesDirectory, packageName)
    let versionPath (version: PackageVersion) =
        Path.Combine(packagePath version.Package, version.Version.ToString())
    let sourcesFile = Path.Combine(sourcesDirectory, "sources.txt")
    let lockFile = Path.Combine(sourcesDirectory, "lockfile")
    
    let lock = new FileLock(lockFile, readonly)
    let RW() =
        if readonly then
            InvalidOperationException() |> raise
    let Update() =
        // use rw = LockRW()
        let sources =
            File.ReadAllLines(sourcesFile)
            |> Array.filter (String.IsNullOrEmpty >> not)
            |> Array.map (fun x -> Uri(x))
        NotImplementedException() |> raise

    member this.AddUri(version, uri) =
        let virt, deps, list = this.VersionInformation(version)
        if List.exists (fun path -> Uri(path) = uri) list then
            ()
        else
            File.AppendAllText(versionPath version, sprintf "%A%c" uri '\n')
    
    override this.Finalize() =
        Dispose(this)
    
    member this.Packages =
        Directory.GetDirectories(sourcesDirectory)
        |> Array.map Path.GetFileName
    
    member this.Add(name) =
        RW()
        let path = packagePath name
        Directory.CreateDirectory(path)
    
    member this.Remove(name) =
        RW()
        Directory.Delete(name)
    
    member this.Remove(name, totally) =
        RW()
        Directory.Delete(name, totally)
    
    member this.Versions package =
        let path = packagePath package
        Directory.GetFiles(path)
        |> Array.map Path.GetFileName
        |> Array.map (fun verstr -> PackageVersion(package, DayVersion.Parse verstr))
    
    member this.VersionInformation (version: PackageVersion) =
        let path = packagePath version.Package
        let path = Path.Combine(path, version.Version.ToString())
        let input = File.ReadAllLines(path) |> List.of_array
        
        let read_links input =
            let rec read_links rest links =
                match rest with
                | "" :: rest ->
                    links
                | [] ->
                    links
                | link :: rest ->
                    let links = link :: links
                    read_links rest links
            
            read_links input []
        
        let read_novirt input =
            let parseDep (input: string) =
                match input.IndexOf(">") with
                | index when index < 0 ->
                    FormatException() |> raise
                | index ->
                    let name = input.Substring(0, index)
                    let version = input.Substring(index + 1)
                    name, DayVersion.Parse version
            
            let rec read_novirt rest deps =
                match rest with
                | "" :: rest ->
                    deps, read_links rest
                | [] ->
                    deps, []
                | dep :: rest ->
                    let dep = parseDep dep
                    let deps = Map.add dep deps
                    read_novirt rest deps
            
            read_novirt input Map.empty
        
        let read_all input =
            match input with
            | "!" :: rest ->
                let deps, links = read_novirt rest
                true, deps, links
            | _ ->
                let deps, links = read_novirt input
                false, deps, links
        
        read_all input
    
    interface IDisposable with
        member this.Dispose() =
            Dispose(lock)